package chianghao.springboot.start.web;

import java.io.IOException;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import com.chianghao.core.exception.HaoException;
import com.chianghao.core.web.R;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

import chianghao.springboot.start.web.properties.CorsProperties;
import chianghao.springboot.start.web.properties.SpringMvcProperties;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableConfigurationProperties({SpringMvcProperties.class,CorsProperties.class})
@ControllerAdvice
@EnableSwagger2
public class SpringMvcAutoConfiguration implements WebMvcConfigurer, ResponseBodyAdvice<Object>  {


    private static Logger logger = LoggerFactory.getLogger(SpringMvcAutoConfiguration.class);

	@Autowired
    private SpringMvcProperties springMvcProperties;

    @Autowired
    private CorsProperties corsProperties;

    
    
    @Bean
	public Docket createRestApi() {
		return new Docket(DocumentationType.SWAGGER_2)
				// 调用apiInfo方法,创建一个ApiInfo实例,里面是展示在文档页面信息内容
				.apiInfo(apiInfo())
				.select()
				// @ApiIgnore 这样,该接口就不会暴露在 swagger2 的页面下
				.apis(RequestHandlerSelectors.basePackage("com.chianghao"))
				.paths(PathSelectors.any()).build();
	}

	private ApiInfo apiInfo() {
		return new ApiInfoBuilder()
				.title("接口文档")
				.version("1.0")
				.description("API描述").build();
	}
	
	@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("swagger-ui.html")
            .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
    
    
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver factory = new CommonsMultipartResolver();
        factory.setMaxUploadSize(springMvcProperties.getMaxUploadSize());
        return factory;
    }

    @Bean
    @ConditionalOnProperty(value = {"chianghao.cors.isCors"}, matchIfMissing = false)
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin(corsProperties.getAllowedOrigin());
        config.addAllowedHeader(corsProperties.getAllowedHeader());
        config.addAllowedMethod(corsProperties.getAllowedMethod());
        source.registerCorsConfiguration(corsProperties.getPathExpression(), config); // CORS 配置对所有接口都有效
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }

    @Bean
    public ObjectMapper objectMapper() {
    	ObjectMapper objectMapper = new ObjectMapper();
		SimpleModule simpleModule = new SimpleModule();
		simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
		simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
		simpleModule.addSerializer(long.class, ToStringSerializer.instance);

		JavaTimeModule javaTimeModule = new JavaTimeModule();
		javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
		javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
		javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));

//        objectMapper.registerModule(simpleModule);
		objectMapper.registerModules(simpleModule,javaTimeModule);
		return objectMapper;
    }

    @Bean
    public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
        // MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(){

			StringHttpMessageConverter shmc = new StringHttpMessageConverter();

			@Override
			public boolean canWrite(Class<?> clazz, MediaType mediaType) {
				boolean canWrite = super.canWrite(clazz, mediaType);
				if (!canWrite) {
					canWrite = clazz.isAssignableFrom(String.class);
				}
				return canWrite;
			}

			@Override
			protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
					throws IOException, HttpMessageNotWritableException {
				if (object != null && object instanceof String) {
					outputMessage.getHeaders().setContentType(MediaType.TEXT_PLAIN);;
					shmc.write((String)object, MediaType.TEXT_PLAIN, outputMessage);
					return;
				}
				super.writeInternal(object, object != null ? object.getClass() : null, outputMessage);
			}

		};
        
        
        jackson2HttpMessageConverter.setObjectMapper(objectMapper());
        return jackson2HttpMessageConverter;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Iterator<HttpMessageConverter<?>> it = converters.iterator();
        while (it.hasNext()) {
            HttpMessageConverter<?> messageConverter = it.next();
            if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
                it.remove();
            }
        }
        converters.add(jackson2HttpMessageConverter());
    }



    @Bean(name = "i18nSourceMap")
    public Map<String,ResourceBundleMessageSource> i18nSourceMap() {
        //默认前缀i18n
        Map<String,ResourceBundleMessageSource> map = new HashMap<String,ResourceBundleMessageSource>();
        if(springMvcProperties.getI18nFileNames()!=null&&springMvcProperties.getI18nFileNames().length>0){
            for(String basename:springMvcProperties.getI18nFileNames()){
                ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
                messageSource.setBasename("i18n/"+basename);
                messageSource.setDefaultEncoding("UTF-8");
                map.put(basename,messageSource);
            }
        }
        I18nUtils.set(map);
        return map;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new HandlerInterceptor() {
			
			@Override
			public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
					throws Exception {
				String language = request.getHeader("language");
				if(StringUtils.isEmpty(language)) {
					language = springMvcProperties.getLanguage();
				}
				request.setAttribute("sessionLanguage",language);
				return true;
			}
		});
	}
    
    private String getLang() {
    	ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
    	String lang = (String) request.getAttribute("sessionLanguage");
    	return lang;
    }
    
    
    private static Map<String,String> languageSplitCharMap = new HashMap<String,String>();
    static {
    	languageSplitCharMap.put("zh","");
    	languageSplitCharMap.put("en"," ");
    }
    
    private String getMessage(String codes) {
    	String lang = getLang();
    	String ls  = "";//语言字于字间的连接符号
    	if(lang.contains("_")) {
    		String l = lang.substring(0, lang.indexOf("_"));
        	if(languageSplitCharMap.containsKey(l)) {
        		ls = languageSplitCharMap.get(l);
        	}
    	}
    	StringBuilder sb = new StringBuilder();
    	for(String code:codes.split("\\|")) {
    		if(code.contains(":")) {
            	String fileName  = code.substring(0,code.indexOf(":"));
            	String keys       = code.substring(code.indexOf(":")+1);
            	String[] keyArray = keys.split(",");
            	for(String key:keyArray) {
            		sb.append(I18nUtils.getMessage(fileName, key, null, lang)+ls);
            	}
            }
    	}
    	if(sb.length()==0) {
    		sb.append(codes);
    	}
    	return sb.toString();
    }
    
  

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public R<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error("参数验证失败", e);
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        //String field = error.getField();
        String message = error.getDefaultMessage();
        return R.getError(getMessage(message));
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException .class)
    @ResponseBody
    public R<?> handleBindException(BindException e) {
    	logger.error("参数绑定失败", e);
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        //String field = error.getField();
        String message = error.getDefaultMessage();
        return R.getError(getMessage(message));
    }


    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public R<?> handleServiceException(ConstraintViolationException e) {
        logger.error("参数验证失败", e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        StringBuilder sb = new StringBuilder();
        while(violations.iterator().hasNext()) {
        	 ConstraintViolation<?> violation = violations.iterator().next();
        	 String message = violation.getMessage();
             sb.append(getMessage(message)+"\n\t");
        }
        return R.getError(sb.toString());
    }

    /**
     * 500
     * @param e
     * @return
     */
    @ExceptionHandler(value = HaoException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public R<?> haoExceptionErrorHandler(HaoException e){
        logger.error("Exception", e);
        String message = e.getMessage();
        return R.getError(e.getCode(),getMessage(message));
    }
    
    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    @ResponseBody
    public R<?> handleValidationException(ValidationException e) {
        logger.error("参数验证失败", e);
        return R.getError("validation.error");
    }

    
    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public R<?> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        logger.error("缺少请求参数", e);
        return R.getError("missing.servlet.request.parameter");
    }
    
    
    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public R<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        logger.error("参数解析失败", e);
        return R.getError("http.not.readable");
    }
    
    /**
     * 404 - Not Found
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseBody
    public R<?> noHandlerFoundException(NoHandlerFoundException e) {
        logger.error("Not Found", e);
        return R.getError("no.handler.found.handler");
    }


    /**
     * 405 - Method Not Allowed
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    public R<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        return R.getError("method.not.allowed");
    }

    /**
     * 415 - Unsupported Media Type
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseBody
    public R<?> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
        logger.error("不支持当前媒体类型", e);
        return R.getError("http.mediatype.not.supported");
        
    }


    /***
     * 500
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public R<?> defaultErrorHandler(Exception e){
        logger.error("Exception", e);
        return R.getError("server.error");
    }

	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		return true;
	}

	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
			ServerHttpResponse response) {
	
		if (body instanceof R){
            return body;
        }
		return R.getSuccess(body);
	}

}
